﻿#region Directives

using ConnectIntegrationHub.WebService.Common;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Threading.Tasks;
using System.Web;

#endregion Directives

namespace ConnectIntegrationHub.WebService
{
    [ServiceContract]
    public class Service
    {
        #region Properties

        [DataMember]
        public String UserAgent
        {
            get;
            set;
        }

        [DataMember]
        public String UserHostName
        {
            get;
            set;
        }

        [DataMember]
        public String UserHostAddress
        {
            get;
            set;
        }

        [DataMember]
        public CRM.Configuration SolutionDeploymentConfiguration
        {
            get;
            set;
        }

        [DataMember]
        public List<CRM.Configuration> Configurations
        {
            get;
            set;
        }

        [DataMember]
        public List<CRM.DeploymentEntity> SolutionDeploymentEntities
        {
            get;
            set;
        }

        [DataMember]
        public List<CRM.DeploymentSynchronization> DeploymentSynchronizations
        {
            get;
            set;
        }

        #endregion Properties

        #region Constuctors

        public Service(Boolean prepareAllConfigurations)
        {
            this.setRequestUserInfo();

            this.prepare(prepareAllConfigurations);
        }

        #endregion Constuctors

        #region Public Methods

        [OperationContract()]
        [WebGet(ResponseFormat = WebMessageFormat.Json)]
        public void processDeploymentSynchronization(Guid deploymentSynchronizationId)
        {
            var deploymentSyncronizationEntityReferenceId = new EntityReference(String.Empty, deploymentSynchronizationId);
            
            var deploymentSyncronization = this.DeploymentSynchronizations.Where(p => p.Id == deploymentSynchronizationId).First();

            var sourceConfiguration = this.Configurations.Where(p => p.Id == deploymentSyncronization.SourceDeploymentConfigurationId).First();
            var destinationConfiguration = this.Configurations.Where(p => p.Id == deploymentSyncronization.DestinationDeploymentconfiguratId).First();

            var order = 1;

            this.createDevelopmentSyncronizationLog(sourceConfiguration, deploymentSyncronizationEntityReferenceId, order++, deploymentSyncronization.DeploymentStartedMessage);

            foreach (var solutionName in deploymentSyncronization.Solutions.Split(Common.Strings.specialCharacter_Comma))
            {
                try
                {
                    this.createDevelopmentSyncronizationLog(sourceConfiguration, deploymentSyncronizationEntityReferenceId, order++, deploymentSyncronization.SolutionExportStartedMessage + Common.Strings.paddingDash + solutionName);

                    var exportSolutionRequest = new ExportSolutionRequest();

                    exportSolutionRequest.Managed = deploymentSyncronization.Managed;
                    exportSolutionRequest.SolutionName = solutionName;

                    var exportSolutionResponse = sourceConfiguration.Service.Execute(exportSolutionRequest) as ExportSolutionResponse;
                    var solutionFileBytes = exportSolutionResponse.ExportSolutionFile;

                    this.createDevelopmentSyncronizationLog(sourceConfiguration, deploymentSyncronizationEntityReferenceId, order++, deploymentSyncronization.SolutionExportCompletedMessage + Common.Strings.paddingDash + solutionName, solutionName + Common.FileExtensions.ZipFile, solutionFileBytes);

                    if (deploymentSyncronization.IncludeSiteMap)
                    {
                        var tempDirectory = Common.Routines.getDirectory(Guid.NewGuid().ToString());
                        var solutionZipFileName = tempDirectory + solutionName + FileExtensions.ZipFile;

                        this.createDevelopmentSyncronizationLog(sourceConfiguration, deploymentSyncronizationEntityReferenceId, order++, deploymentSyncronization.SiteMapStartedMessage + Common.Strings.paddingDash + solutionName, solutionName + Common.FileExtensions.ZipFile, solutionFileBytes);

                        System.IO.File.WriteAllBytes(solutionZipFileName, solutionFileBytes);

                        Common.ZipFiles.extract(solutionZipFileName, tempDirectory);

                        var customizationsXmlFileName = tempDirectory + Common.Constants.customizations_Xml_Filename;
                        var customizationsXmlFileContents = System.IO.File.ReadAllText(customizationsXmlFileName);

                        customizationsXmlFileContents =
                            customizationsXmlFileContents.Replace(
                                
                                Common.Constants.EntityMaps_Xml_Tag,

                                deploymentSyncronization.SiteMap +

                                Common.Strings.specialCharacter_Carriage_Return +
                                Common.Strings.specialCharacter_Carriage_Return +

                                Common.Constants.EntityMaps_Xml_Tag

                            );

                        System.IO.File.WriteAllText(customizationsXmlFileName, customizationsXmlFileContents);

                        ZipFiles.create(solutionZipFileName, tempDirectory);
                        solutionFileBytes = System.IO.File.ReadAllBytes(solutionZipFileName);

                        this.createDevelopmentSyncronizationLog(sourceConfiguration, deploymentSyncronizationEntityReferenceId, order++, deploymentSyncronization.SiteMapCompletedMessage + Common.Strings.paddingDash + solutionName, solutionName + Common.FileExtensions.ZipFile, solutionFileBytes);

                        try
                        {
                            System.IO.Directory.Delete(tempDirectory, true);
                        }
                        catch { }
                    }

                    var importSolutionRequest =
                        new ImportSolutionRequest()
                        {
                            CustomizationFile = solutionFileBytes
                        };

                    this.createDevelopmentSyncronizationLog(sourceConfiguration, deploymentSyncronizationEntityReferenceId, order++, deploymentSyncronization.SolutionImportStartedMessage + Common.Strings.paddingDash + solutionName);

                    destinationConfiguration.Service.Execute(importSolutionRequest);

                    this.createDevelopmentSyncronizationLog(sourceConfiguration, deploymentSyncronizationEntityReferenceId, order++, deploymentSyncronization.SolutionImportCompletedMessage + Common.Strings.paddingDash + solutionName, solutionName + Common.FileExtensions.ZipFile, solutionFileBytes);
                }
                catch { }
            }

            var deploymentEntities =
                (
                    from deploymentEntity in this.SolutionDeploymentEntities
                    where deploymentEntity.DeploymentSynchronizationId == deploymentSyncronization.Id
                    select deploymentEntity
                ).ToList();

            if (deploymentSyncronization.Clear)
            {
                foreach (var deploymentEntity in deploymentEntities.OrderByDescending(p => p.Order))
                {
                    this.createDevelopmentSyncronizationLog(sourceConfiguration, deploymentSyncronizationEntityReferenceId, order++, deploymentSyncronization.DataClearStartedMessage + Common.Strings.paddingDash + deploymentEntity);
                    foreach (var record in getEntityData(destinationConfiguration.Service, deploymentEntity.Name))
                    {
                        try
                        {
                            destinationConfiguration.Service.Delete(deploymentEntity.Name, record.Id);
                        }
                        catch { }
                    };
                    this.createDevelopmentSyncronizationLog(sourceConfiguration, deploymentSyncronizationEntityReferenceId, order++, deploymentSyncronization.DataClearCompletedMessage + Common.Strings.paddingDash + deploymentEntity);
                }
            }

            foreach (var deploymentEntity in deploymentEntities.OrderBy(p => p.Order))
            {
                this.createDevelopmentSyncronizationLog(sourceConfiguration, deploymentSyncronizationEntityReferenceId, order++, deploymentSyncronization.DataCopyStartedMessage + Common.Strings.paddingDash + deploymentEntity.Name);

                copyEntities(deploymentSyncronization, deploymentEntity, sourceConfiguration, destinationConfiguration);

                this.createDevelopmentSyncronizationLog(sourceConfiguration, deploymentSyncronizationEntityReferenceId, order++, deploymentSyncronization.DataCopyCompletedMessage + Common.Strings.paddingDash + deploymentEntity.Name);
            }
            this.createDevelopmentSyncronizationLog(sourceConfiguration, deploymentSyncronizationEntityReferenceId, order++, deploymentSyncronization.DeploymentCompletedMessage);
        }

        [OperationContract()]
        [WebGet(ResponseFormat = WebMessageFormat.Json)]
        public List<Entity> getEntityData(String entityName, List<String> fieldNames = null, List<ConditionExpression> conditions = null, Int32? recordCount = null, Boolean includeStateCode = true)
        {
            return this.getEntityData(this.SolutionDeploymentConfiguration.Service, entityName, fieldNames, conditions, recordCount, includeStateCode);
        }

        [OperationContract()]
        [WebGet(ResponseFormat = WebMessageFormat.Json)]
        public List<Entity> getEntityData(IOrganizationService service, String entityName, List<String> fieldNames = null, List<ConditionExpression> conditions = null, Int32? recordCount = null, Boolean includeStateCode = true)
        {
            var result = new List<Entity>();
            var expression = new QueryExpression(entityName);

            expression.ColumnSet.AllColumns = true;
            if (fieldNames != null && fieldNames.Count > 0)
            {
                expression.ColumnSet = new ColumnSet(fieldNames.ToArray());
            }

            var filter = new FilterExpression();
            if (conditions != null && conditions.Count > 0)
            {
                foreach (var condition in conditions)
                {
                    filter.Conditions.Add(condition);
                }
            }

            if (includeStateCode)
            {
                var condition = new ConditionExpression();

                condition.AttributeName = CRM.Fields.statecode;
                condition.Operator = ConditionOperator.Equal;
                condition.Values.Add(Common.StateCodes.Active);

                filter.Conditions.Add(condition);
            }
            expression.Criteria = filter;

            expression.PageInfo = new PagingInfo();
            if (recordCount.HasValue)
            {
                expression.TopCount = recordCount.Value;
            }
            else
            {
                expression.PageInfo.ReturnTotalRecordCount = true;

                expression.PageInfo.Count = 1000;
                expression.PageInfo.PageNumber = 1;
                expression.PageInfo.PagingCookie = null;
            }

            while (true)
            {
                var entityQuery = service.RetrieveMultiple(expression);
                foreach (var entity in entityQuery.Entities)
                {
                    result.Add(entity);
                }
                if (entityQuery.MoreRecords)
                {
                    expression.PageInfo.PageNumber++;
                    expression.PageInfo.PagingCookie = entityQuery.PagingCookie;
                }
                else
                {
                    break;
                }
            }
            return result;
        }

        #endregion Public Methods

        #region Private Methods

        /// <summary>
        /// Copies Entities
        /// </summary>
        /// <param name="entityName">Entity Name</param>
        /// <param name="sourceService">Source</param>
        /// <param name="destinationService">Destination</param>
        private void copyEntities(CRM.DeploymentSynchronization deploymentSyncronization, CRM.DeploymentEntity deploymentEntity, CRM.Configuration sourceConfiguration, CRM.Configuration destinationConfiguration, List<ConditionExpression> conditions = null)
        {
            var sourceService = sourceConfiguration.Service;
            var destinationService = destinationConfiguration.Service;

            var prefixes = deploymentSyncronization.Prefixes.Split(Common.Strings.specialCharacter_Comma);
            var excludedAttributes = deploymentEntity.ExcludedAttributes.Split(Common.Strings.specialCharacter_Comma);

            try
            {
                foreach (var entity in this.getEntityData(sourceService, deploymentEntity.Name, conditions: conditions))
                {
                    var newEntity = new Entity(deploymentEntity.Name);
                    newEntity.Id = entity.Id;

                    foreach (var attribute in entity.Attributes)
                    {
                        var query =
                           from prefix in prefixes
                           where attribute.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
                           select prefix;

                        if (query.Any())
                        {
                            query =
                               from excludedAttribute in excludedAttributes
                               where attribute.Key.StartsWith(excludedAttribute, StringComparison.OrdinalIgnoreCase)
                               select excludedAttribute;

                            if (!query.Any())
                            {
                                newEntity.Attributes.Add(attribute);
                            }
                        }
                    }

                    try
                    {
                        if (deploymentEntity.Action == CRM.DeploymentEntity.Actions.Create)
                        {
                            destinationService.Create(newEntity);
                        }
                        else if (deploymentEntity.Action == CRM.DeploymentEntity.Actions.Update)
                        {
                            destinationService.Update(newEntity);
                        }
                    }
                    catch { }
                };
            }
            catch { }            
        }

        /// <summary>
        /// Prepares All.
        /// </summary>
        private void prepare(Boolean prepareAllConfigurations = true)
        {
            var configurationName = Common.Routines.getCRMConfigurationName();
            var applicationId = Common.Routines.getCRMApplicationId();
            var installationType = Common.Routines.getCRMInstallationType();
            var organizationUri = Common.Routines.getCRMOrganizationUri();
            var userName = Common.Routines.getCRMUserName();
            var password = Common.Routines.getCRMPassword();

            this.SolutionDeploymentConfiguration =
                new CRM.Configuration(
                    System.Guid.NewGuid(),
                    configurationName,
                    installationType,
                    organizationUri,
                    applicationId,
                    userName,
                    password
                );

            var internalService = this.SolutionDeploymentConfiguration.Service;

            this.DeploymentSynchronizations = new List<CRM.DeploymentSynchronization>();
            this.SolutionDeploymentEntities = new List<CRM.DeploymentEntity>();
            this.Configurations = new List<CRM.Configuration>();

            if (prepareAllConfigurations)
            {
                var entities = this.getEntityData(internalService, CRM.Entities.DeploymentSynchronization).OrderBy(p => p.Attributes[CRM.Fields.Order]);
                foreach (var entity in entities)
                {
                    this.DeploymentSynchronizations.Add(
                        new CRM.DeploymentSynchronization()
                        {
                            Id = entity.Id,
                            SourceDeploymentConfigurationId = Common.Routines.getAttributeEntityReference(entity, CRM.Fields.SourceDeploymentConfigurationId).Id,
                            DestinationDeploymentconfiguratId = Common.Routines.getAttributeEntityReference(entity, CRM.Fields.DestinationDeploymentConfiguratId).Id,

                            Order = Common.Routines.getAttributeInteger(entity, CRM.Fields.Order),
                            Name = Common.Routines.getAttributeString(entity, CRM.Fields.Name),
                            Solutions = Common.Routines.getAttributeString(entity, CRM.Fields.Solutions),
                            Managed = Common.Routines.getAttributeBoolean(entity, CRM.Fields.Managed),
                            Prefixes = Common.Routines.getAttributeString(entity, CRM.Fields.Prefixes),
                            Clear = Common.Routines.getAttributeBoolean(entity, CRM.Fields.Clear),
                            
                            DeploymentStartedMessage = Common.Routines.getAttributeString(entity, CRM.Fields.DeploymentStartedMessage),
                            DeploymentCompletedMessage = Common.Routines.getAttributeString(entity, CRM.Fields.DeploymentCompletedMessage),
                            
                            SolutionExportStartedMessage = Common.Routines.getAttributeString(entity, CRM.Fields.SolutionExportStartedMessage),
                            SolutionExportCompletedMessage = Common.Routines.getAttributeString(entity, CRM.Fields.SolutionExportCompletedMessage),
                            
                            SolutionImportStartedMessage = Common.Routines.getAttributeString(entity, CRM.Fields.SolutionImportStartedMessage),
                            SolutionImportCompletedMessage = Common.Routines.getAttributeString(entity, CRM.Fields.SolutionImportCompletedMessage),
                            
                            DataClearStartedMessage = Common.Routines.getAttributeString(entity, CRM.Fields.DataClearStartedMessage),
                            DataClearCompletedMessage = Common.Routines.getAttributeString(entity, CRM.Fields.DataClearCompletedMessage),
                            
                            DataCopyStartedMessage = Common.Routines.getAttributeString(entity, CRM.Fields.DataCopyStartedMessage),
                            DataCopyCompletedMessage = Common.Routines.getAttributeString(entity, CRM.Fields.DataCopyCompletedMessage),
                            
                            SiteMapStartedMessage = Common.Routines.getAttributeString(entity, CRM.Fields.SiteMapStartedMessage),
                            SiteMapCompletedMessage = Common.Routines.getAttributeString(entity, CRM.Fields.SiteMapCompletedMessage),

                            IncludeSiteMap = Common.Routines.getAttributeBoolean(entity, CRM.Fields.IncludeSiteMap),
                            SiteMap = Common.Routines.getAttributeString(entity, CRM.Fields.SiteMap)
                        }
                    );
                }

                entities = this.getEntityData(internalService, CRM.Entities.DeploymentEntity).OrderBy(p => p.Attributes[CRM.Fields.Order]);
                foreach (var entity in entities)
                {
                    this.SolutionDeploymentEntities.Add(
                        new CRM.DeploymentEntity()
                        {
                            DeploymentSynchronizationId = Common.Routines.getAttributeEntityReference(entity, CRM.Fields.DeploymentSynchronizationId).Id,
                            Order = Common.Routines.getAttributeInteger(entity, CRM.Fields.Order),
                            Name = Common.Routines.getAttributeString(entity, CRM.Fields.Name),
                            ExcludedAttributes = Common.Routines.getAttributeString(entity, CRM.Fields.ExcludedAttributes),
                            Action = (CRM.DeploymentEntity.Actions) Common.Routines.getAttributeOptionSetInteger(entity, CRM.Fields.Action)
                        }
                    );
                }

                var deploymentConfigurations = this.getEntityData(internalService, CRM.Entities.DeploymentConfiguration);
                foreach (var configuration in deploymentConfigurations)
                {
                    this.Configurations.Add(
                        new CRM.Configuration(
                            configuration.Id,
                            Common.Routines.getAttributeString(configuration, CRM.Fields.Name),
                            (CRM.Configuration.InstallationTypes) Common.Routines.getAttributeOptionSetInteger(configuration, CRM.Fields.InstallationType),
                            Common.Routines.getAttributeString(configuration, CRM.Fields.OrganizationUri),
                            new Guid(Common.Routines.getAttributeString(configuration, CRM.Fields.ApplicationId)),
                            Common.Routines.getAttributeString(configuration, CRM.Fields.UserName),
                            Common.Routines.getAttributeString(configuration, CRM.Fields.Password)
                        )
                    );
                }
            }
        }

        private void createDevelopmentSyncronizationLog(CRM.Configuration configuration, EntityReference deploymentSyncronizationId, Int32 order, String description, String fileName = null, Byte[] fileBytes = null)
        {
            var syncronizationLog = new Entity(CRM.Entities.DeploymentSynchronizationLog);

            syncronizationLog.Attributes.Add(CRM.Fields.DevelopmentSynchronizationId, deploymentSyncronizationId);
            syncronizationLog.Attributes.Add(CRM.Fields.Order, order);
            syncronizationLog.Attributes.Add(CRM.Fields.UserAgent, this.UserAgent.IsNullOrEmpty() ? Constants.UnKnown : this.UserAgent);
            syncronizationLog.Attributes.Add(CRM.Fields.HostAddress, this.UserHostAddress.IsNullOrEmpty() ? Constants.UnKnown : this.UserHostAddress);
            syncronizationLog.Attributes.Add(CRM.Fields.UserHostName, this.UserHostName.IsNullOrEmpty() ? Constants.UnKnown : this.UserHostName);
            syncronizationLog.Attributes.Add(CRM.Fields.Name, description);

            var syncronizationLogId = new EntityReference(String.Empty, configuration.Service.Create(syncronizationLog));

            if (!fileName.IsNullOrEmpty() && fileBytes != null)
            {
                var annotation = new Entity(CRM.Entities.annotation);

                annotation.Attributes.Add(CRM.Fields.IsDocument, true);
                annotation.Attributes.Add(CRM.Fields.Subject, fileName);
                annotation.Attributes.Add(CRM.Fields.NoteText, fileName);
                annotation.Attributes.Add(CRM.Fields.DocumentBody, Convert.ToBase64String(fileBytes));
                annotation.Attributes.Add(CRM.Fields.FileName, fileName);
                annotation.Attributes.Add(CRM.Fields.FileSize, fileBytes.Length);
                annotation.Attributes.Add(CRM.Fields.MimeType, Common.MimeTypes.Zip);
                annotation.Attributes.Add(CRM.Fields.ObjectId, syncronizationLogId);
                annotation.Attributes.Add(CRM.Fields.ObjectTypeCode, CRM.Entities.DeploymentSynchronizationLog);

                configuration.Service.Create(annotation);
            }
        }

        private void setRequestUserInfo()
        {
            this.UserAgent = Common.Constants.UnKnown;
            this.UserHostAddress = Common.Constants.UnKnown;
            this.UserHostName = Common.Constants.UnKnown;

            if (HttpContext.Current.Request != null)
            {
                this.UserAgent = HttpContext.Current.Request.UserAgent;
                this.UserHostAddress = HttpContext.Current.Request.UserHostAddress;
                this.UserHostName = HttpContext.Current.Request.UserHostName;
            }

            if (HttpContext.Current.Request.IsLocal)
            {
                this.UserHostAddress = Common.Constants.Local;
                this.UserHostName = Common.Constants.Local;
            }
        }

        #endregion Private Methods
    }
}